contents
๐ ์ฝ๋ฉ ํ ์คํธ๋ฅผ ์ํ ์ฌ๊ท(Recursion) & ๋ฐฑํธ๋ํน(Backtracking) ๊ฐ์ด๋
์ฌ๊ธฐ์๋ ๊ฐ๋ , ์ฐจ์ด์ , ๋ํ ๋ฌธ์ ์ ํ, Java ์คํ์ผ ์ฝ๋ ์์ ๋ก ์ฌ๊ท์ ๋ฐฑํธ๋ํน์ ๊น์ด ์๊ฒ ์ ๋ฆฌํฉ๋๋ค.
1. ์ฌ๊ท(Recursion)๋?
์ ์:
ํจ์๊ฐ ์๊ธฐ ์์ ์ ํธ์ถํ์ฌ ๋ฌธ์ ๋ฅผ ๋ ์์ ํ์ ๋ฌธ์ ๋ก ์ชผ๊ฐ์ด ํด๊ฒฐํ๋ ๊ธฐ๋ฒ์
๋๋ค.
- ํต์ฌ ์๋ฆฌ: ํฐ ๋ฌธ์ ๋ฅผ ๋์ผํ ์ฑ์ง์ ์์ ๋ฌธ์ ๋ก ๋๋ ์ ํ๊ณ , **๊ธฐ์ ์กฐ๊ฑด(Base Case)**์์ ์ข ๋ฃ.
- ๋ํ ์์: ํฉํ ๋ฆฌ์ผ, ํผ๋ณด๋์น, ํธ๋ฆฌ ์ํ
์์ โ ํฉํ ๋ฆฌ์ผ
int factorial(int n) {
if (n == 0) return 1; // ๊ธฐ์ ์กฐ๊ฑด
return n * factorial(n - 1); // ์ฌ๊ท ํธ์ถ
}
Dry Run (n=3):
- factorial(3) โ 3 * factorial(2)
- factorial(2) โ 2 * factorial(1)
- factorial(1) โ 1 * factorial(0)
- factorial(0) โ 1
- ์ต์ข ๊ฐ: 6
2. ๋ฐฑํธ๋ํน(Backtracking)์ด๋?
์ ์:
๋ชจ๋ ๊ฐ๋ฅํ ๊ฒฝ์ฐ์ ์(์ ํ์ง)๋ฅผ ํ์ํ๋ฉด์, ์กฐ๊ฑด์ ๋ง์ง ์์ผ๋ฉด ๋๋์๊ฐ์(Backtrack) ๋ค๋ฅธ ๊ธธ์ ์ฐพ๋ ๋ฐฉ์์ ์ฌ๊ท ํ์ ๊ธฐ๋ฒ์
๋๋ค.
- ํต์ฌ ์๋ฆฌ: ๊ฐ๋ฅํ ์ ํ์ง๋ฅผ ์ฌ๊ท์ ์ผ๋ก ๋ชจ๋ ์๋ โ ์คํจ ์ ์ด์ ์ํ๋ก ๋ณต์(undo) ํ ๋ค๋ฅธ ๊ฐ์ง(branch) ํ์.
- ๋ํ ๋ฌธ์ : N-Queen, ์ค๋์ฟ , ๋ถ๋ถ์งํฉ, ์์ด, ๋จ์ด ์ฐพ๊ธฐ
3. ์ฌ๊ท vs ๋ฐฑํธ๋ํน
- ๊ณตํต์ : ๋ ๋ค ํจ์๊ฐ ์๊ธฐ ์์ ์ ํธ์ถํ๋ ๊ตฌ์กฐ.
- ์ฐจ์ด์ :
- ์ฌ๊ท๋ ๋ณดํต ํ ํด๋ต์ด๋ ๊ฐ์ ๊ตฌํจ.
- ๋ฐฑํธ๋ํน์ ์ฌ๋ฌ ํด๋ต/๋ชจ๋ ๊ฒฝ์ฐ์ ์๋ฅผ ํ์ + ์ ์ฝ ์กฐ๊ฑด์ ๋ง์กฑํ๋ ํด๋ง ์ ํ.
4. ๋ํ ๋ฐฑํธ๋ํน ๋ฌธ์ & ์ฝ๋
a. N-Queen ๋ฌธ์
void solveNQueens(int n) {
List<List<String>> solutions = new ArrayList<>();
char[][] board = new char[n][n];
for (char[] row : board) Arrays.fill(row, '.');
backtrack(board, 0, solutions);
}
void backtrack(char[][] board, int row, List<List<String>> solutions) {
if (row == board.length) {
solutions.add(boardToList(board));
return;
}
for (int col = 0; col < board.length; col++) {
if (isValid(board, row, col)) {
board[row][col] = 'Q';
backtrack(board, row + 1, solutions);
board[row][col] = '.'; // ์ํ ๋ณต์
}
}
}
boolean isValid(char[][] board, int row, int col) {
for (int i = 0; i < row; i++)
if (board[i][col] == 'Q') return false;
for (int i = row-1, j = col-1; i>=0 && j>=0; i--, j--)
if (board[i][j] == 'Q') return false;
for (int i = row-1, j = col+1; i>=0 && j<board.length; i--, j++)
if (board[i][j] == 'Q') return false;
return true;
}
b. ์ค๋์ฟ ํด๊ฒฐ
boolean solveSudoku(char[][] board) {
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
if (board[i][j] == '.') {
for (char c = '1'; c <= '9'; c++) {
if (isValid(board, i, j, c)) {
board[i][j] = c;
if (solveSudoku(board)) return true;
board[i][j] = '.'; // ๋ฐฑํธ๋ํน
}
}
return false;
}
return true;
}
boolean isValid(char[][] b, int r, int c, char num) {
for (int i = 0; i < 9; i++) {
if (b[r][i] == num || b[i][c] == num) return false;
if (b[3*(r/3)+i/3][3*(c/3)+i%3] == num) return false;
}
return true;
}
c. ๋ถ๋ถ์งํฉ ์์ฑ
List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
backtrack(nums, 0, new ArrayList<>(), res);
return res;
}
void backtrack(int[] nums, int start, List<Integer> track, List<List<Integer>> res) {
res.add(new ArrayList<>(track));
for (int i = start; i < nums.length; i++) {
track.add(nums[i]);
backtrack(nums, i+1, track, res);
track.remove(track.size()-1);
}
}
d. ์์ด ์์ฑ
List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
backtrack(nums, new ArrayList<>(), new boolean[nums.length], res);
return res;
}
void backtrack(int[] nums, List<Integer> track, boolean[] used, List<List<Integer>> res) {
if (track.size() == nums.length) {
res.add(new ArrayList<>(track));
return;
}
for (int i = 0; i < nums.length; i++) {
if (!used[i]) {
track.add(nums[i]);
used[i] = true;
backtrack(nums, track, used, res);
track.remove(track.size()-1);
used[i] = false;
}
}
}
e. ๋จ์ด ์ฐพ๊ธฐ (Word Search)
boolean exist(char[][] board, String word) {
for (int i = 0; i < board.length; i++)
for (int j = 0; j < board[0].length; j++)
if (dfs(board, word, i, j, 0)) return true;
return false;
}
boolean dfs(char[][] board, String word, int i, int j, int idx) {
if (idx == word.length()) return true;
if (i<0 || j<0 || i>=board.length || j>=board[0].length || board[i][j] != word.charAt(idx))
return false;
char tmp = board[i][j];
board[i][j] = '#';
boolean found = dfs(board, word, i+1, j, idx+1) ||
dfs(board, word, i-1, j, idx+1) ||
dfs(board, word, i, j+1, idx+1) ||
dfs(board, word, i, j-1, idx+1);
board[i][j] = tmp; // ์ํ ๋ณต์
return found;
}
5. ๋ฐฑํธ๋ํน ํ ํ๋ฆฟ
void backtrack(...) {
if (ํด๋ต ์กฐ๊ฑด ์ถฉ์กฑ) {
์ ์ฅ/์ถ๋ ฅ;
return;
}
for (๊ฐ๋ฅํ ์ ํ : ๋ชจ๋ ํ๋ณด) {
if (์กฐ๊ฑด ๊ฒ์ฆ) {
์ํ ๋ณ๊ฒฝ;
backtrack(...);
์ํ ๋ณต์;
}
}
}
6. ํต์ฌ ํ
- ์ํ ์ถ์ : ๋ฐฐ์ด, ๋ฆฌ์คํธ, visited[] ํ์ฉ
- ๊ฐ์ง์น๊ธฐ(Pruning): ๋ถ๊ฐ๋ฅํ ๊ฒฝ์ฐ๋ ์ผ์ฐ ๋ฒ๋ฆผ
- ์ํ ๋ณต์: ์ฌ๊ท ํธ์ถ ํ ๋ฐ๋์ ์ด์ ์ํ๋ก ๋๋๋ฆผ
- ์ฌ๊ทํธ๋ฆฌ: ๊ฐ ๋ ธ๋๊ฐ ํ๋์ ์ํ / ๊ฐ ๊ฐ์ง๊ฐ ์ ํ ๊ฒฝ๋ก
7. ์ฌ์ฉ ์์
- ๋ชจ๋ ๊ฒฝ์ฐ์ ์/์กฐํฉ/์์ด ํ์
- ์ ์ฝ ์กฐ๊ฑด์ด ์๋ ๋ฌธ์ (์: N-Queen)
- ๋ฏธ๋กยท๊ทธ๋ํ ๊ฒฝ๋ก ์ฐพ๊ธฐ
- ์์ฌ๊ฒฐ์ ํธ๋ฆฌ ์์ฑ, ์ค์ฒฉ ๊ตฌ์กฐ ํ์
8. ๋ณต์ก๋
๋๋ถ๋ถ ์ง์ ์๊ฐ ๋ณต์ก๋ O(kโฟ) ๋๋ O(n!)
โ ๊ฐ์ง์น๊ธฐยท๋ฉ๋ชจ์ด์ ์ด์
์ผ๋ก ๊ฐ๋ฅํ๋ฉด ์ค์ด๊ธฐ
9. ์์ฝ ํ
| ์ ํ | ์์ | ์ ๊ทผ |
|---|---|---|
| ๋จ์ ์ฌ๊ท | ํฉํ ๋ฆฌ์ผ, ํผ๋ณด๋์น | ๊ธฐ์ ์กฐ๊ฑด & ์๊ธฐ ํธ์ถ |
| ๋ถ๋ถ์งํฉ | โ ๋ชจ๋ ๋ถ๋ถ์งํฉ | ์ ํ/๋น์ ํ ์ฌ๊ท ํ์ |
| ์์ด | โ ๋ชจ๋ ์์ด | used[]๋ก ์ค๋ณต ๋ฐฉ์ง, ๋ฐฑํธ๋ํน |
| ๋ณด๋ ํผ์ฆ | N-Queen, Sudoku | ์ ํจ์ฑ ๊ฒ์ฌ ํ ๋ฐฐ์นยท๋ณต๊ตฌ ๋ฐ๋ณต |
| ๊ฒฝ๋ก ํ์ | ๋จ์ด์ฐพ๊ธฐ, ๋ฏธ๋ก ํ์ | DFS, ๋ฐฉ๋ฌธ ํ์, ์ํ ๋ณต์ |
๐ฅ ์ฝ๋ฉ ํ ์คํธ Recursion/Backtracking(์ฌ๊ท/๋ฐฑํธ๋ํน) ๋ํ ๋ฌธ์ & ํ์ด ์ ๋ฆฌ
์๋๋ LeetCodeยทํ๋ก๊ทธ๋๋จธ์คยท๊ธฐ์ ๋ฉด์ ๋ฑ์์ ๋งค์ฐ ์์ฃผ ๋ฑ์ฅํ๋ ์ฌ๊ทยท๋ฐฑํธ๋ํน ์ ํ ๋ํ ๋ฌธ์ ์ ๊ธฐ๋ณธ ํ์ด ์ฝ๋/ํต์ฌ ์์ด๋์ด๋ฅผ ์ ๋ฆฌํ ๋ด์ฉ์ ๋๋ค.
1. ๋ชจ๋ ๋ถ๋ถ์งํฉ(Powerset) ๊ตฌํ๊ธฐ
๋ฌธ์ :
์ ์ ์งํฉ์ด ์ฃผ์ด์ง ๋, ๋ชจ๋ ๋ถ๋ถ์งํฉ(ํ์์
)์ ๋ฐํํ์ธ์.
ํ์ด:
๊ฐ ์์์ ๋ํด โํฌํจ/๋ถํฌํจโ์ ์ ํํ๋ฉฐ ๋ฐฑํธ๋ํน(์ฌ๊ท ํ์).
void dfs(int[] nums, int idx, List<Integer> path, List<List<Integer>> res) {
res.add(new ArrayList<>(path)); // ํ์ฌ ๋ถ๋ถ์งํฉ ์ ์ฅ
for (int i = idx; i < nums.length; i++) {
path.add(nums[i]);
dfs(nums, i+1, path, res); // ๋ค์ ๋จ๊ณ๋ก ์ด๋
path.remove(path.size()-1); // ๋ฐฑํธ๋ํน
}
}
2. ์์ด(Permutations)
๋ฌธ์ :
์์ด์ด ์ฃผ์ด์ง ๋, ๊ฐ๋ฅํ ๋ชจ๋ ์์ด์ ๋ฐํํ์ธ์.
ํ์ด:
์ฌ๊ท + used[] ๋ฐฐ์ด๋ก ์ค๋ณต ๋ฐฉ์ง. ์ฌ์ฉ ์ํ ์์๋ง๋ค ์๋ก์ด ์๋ฆฌ์ ์๋.
void dfs(int[] nums, List<Integer> path, boolean[] used, List<List<Integer>> res) {
if (path.size() == nums.length) res.add(new ArrayList<>(path));
else {
for (int i = 0; i < nums.length; i++) {
if (!used[i]) {
used[i] = true;
path.add(nums[i]);
dfs(nums, path, used, res);
path.remove(path.size()-1); // ๋ฐฑํธ๋ํน
used[i] = false;
}
}
}
}
3. N-Queen
๋ฌธ์ :
NรN ์ฒด์คํ์ ์๋ก๊ฐ ๊ณต๊ฒฉํ์ง ์๋ N๊ฐ์ ํธ์ ๋ฐฐ์นํ๋ ๋ชจ๋ ๊ฒฝ์ฐ(๋ฐฐ์น)๋ฅผ ๋ฐํํ์ธ์.
ํ์ด:
๊ฐ ํ(row)๋ง๋ค ๊ฐ๋ฅํ ์์น์ ํธ์ ๋๊ณ , ์ ํจ์ฑ ์ฒดํฌ(์ด, ๋๊ฐ์ ).
ํธ์ ๋๊ณ ๋ค์ ํ ์งํ, ๋ถ๊ฐ๋ฅํ๋ฉด ๋๋๋ฆผ (๋ฐฑํธ๋ํน).
void dfs(char[][] board, int row, List<List<String>> res) {
if (row == board.length) res.add(boardToList(board));
else {
for (int col = 0; col < board.length; col++) {
if (isValid(board, row, col)) {
board[row][col] = 'Q';
dfs(board, row+1, res);
board[row][col] = '.'; // ๋ฐฑํธ๋ํน
}
}
}
}
4. ์ค๋์ฟ (Sudoku) ํ์ด
๋ฌธ์ :
9ร9 ์ค๋์ฟ ํผ์ฆ(๊ณต๋ฐฑ: '.')์ ์ฑ์ฐ์ธ์.
ํ์ด:
๋ชจ๋ ์
์ 1~9 ์ซ์ ์๋, ์ ํจ(ํ/์ด/์นธ ์ฒดํฌ)ํ๋ฉด ์ฌ๊ท ์งํ, ๋ถ๊ฐ๋ฅํ๋ฉด ๋๋๋ฆผ.
boolean solve(char[][] board) {
for (int r = 0; r < 9; r++)
for (int c = 0; c < 9; c++)
if (board[r][c] == '.') {
for (char num = '1'; num <= '9'; num++)
if (isValid(board, r, c, num)) {
board[r][c] = num;
if (solve(board)) return true;
board[r][c] = '.'; // ๋ฐฑํธ๋ํน
}
return false;
}
return true;
}
5. Word Search (๋จ์ด์ฐพ๊ธฐ/๊ฒฉ์ ๊ฒฝ๋ก์ฐพ๊ธฐ)
๋ฌธ์ :
2์ฐจ์ ๋ฌธ์ํ(board)๊ณผ ๋จ์ด(word)๊ฐ ์ฃผ์ด์ง ๋, ์ธ์ (์ํ์ข์ฐ) ์
์ ๋ฐ๋ผ ํด๋น ๋จ์ด๋ฅผ ์์ฑํ ์ ์๋์ง ํ๋ณํ์ธ์.
ํ์ด:
๋ชจ๋ ์
์์์ ์์ DFS+๋ฐฑํธ๋ํน. ๋ฐฉ๋ฌธ์ฒดํฌ, ๋ค ๋ฐฉํฅ ์ฌ๊ท, ์คํจ ์ ์์๋ณต๊ตฌ.
boolean dfs(char[][] board, String word, int i, int j, int idx) {
if (idx == word.length()) return true;
if (i<0 || j<0 || i>=board.length || j>=board[0].length || board[i][j] != word.charAt(idx)) return false;
char tmp = board[i][j]; board[i][j] = '#';
boolean found = dfs(board, word, i+1, j, idx+1) ||
dfs(board, word, i-1, j, idx+1) ||
dfs(board, word, i, j+1, idx+1) ||
dfs(board, word, i, j-1, idx+1);
board[i][j] = tmp; return found;
}
6. Combination Sum / Subset Sum
๋ฌธ์ :
์ ์ candidates์ target์ด ์ฃผ์ด์ง ๋, ๊ฐ ์์๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉํ ์ ์๋ ํฉ์ด target์ด ๋๋ ๋ชจ๋ ์กฐํฉ์ ๊ตฌํ์ธ์.
ํ์ด:
๊ฐ ์์๋ฅผ ๊ณ ๋ฅด๊ฑฐ๋, ์คํตํ๊ฑฐ๋. ์ ํ์ target ๊ฐ์, ๋ฐฑํธ๋ํน.
void dfs(int[] candidates, int target, int idx, List<Integer> path, List<List<Integer>> res) {
if (target == 0) res.add(new ArrayList<>(path));
else if (target > 0) {
for (int i = idx; i < candidates.length; i++) {
path.add(candidates[i]);
dfs(candidates, target-candidates[i], i, path, res); // ๊ฐ์ ๊ฐ ์ฌ์ฌ์ฉ ํ์ฉ
path.remove(path.size()-1); // ๋ฐฑํธ๋ํน
}
}
}
7. ์ฌ๋ฐ๋ฅธ ๊ดํธ ์กฐํฉ ์์ฑ (Generate Parentheses)
๋ฌธ์ :
N์์ ๊ดํธ(N pairs)๊ฐ ์ฃผ์ด์ง ๋, ๋ชจ๋ ์ฌ๋ฐ๋ฅธ ์กฐํฉ์ ์์ฑํ์ธ์.
ํ์ด:
์ผ์ชฝ ๊ดํธ(open), ์ค๋ฅธ์ชฝ ๊ดํธ(close) ๊ฐ์๋ฅผ ์ถ์ . open < N์ด๋ฉด '(', close < open์ด๋ฉด ')' ์ถ๊ฐ. ๊ธธ์ด=2N์ด๋ฉด ๊ธฐ๋ก.
void dfs(int n, int open, int close, String path, List<String> res) {
if (path.length() == 2*n) res.add(path);
if (open < n) dfs(n, open+1, close, path+"(", res);
if (close < open) dfs(n, open, close+1, path+")", res);
}
โ ๋ฉด์ /์ฝํ ์ฑ์ ํฌ์ธํธ ์์ฝ
| ๋ฌธ์ ์ ํ | ํต์ฌ ๋ฐฑํธ๋ํน/์ฌ๊ท ๊ตฌ์กฐ | ์ค๋ช ํด์ผ ํ ํฌ์ธํธ |
|---|---|---|
| ๋ถ๋ถ์งํฉ | ํฌํจ/๋นํฌํจ ํ์, ๊ฒฝ๋ก ์ถ์ | ์ฌ๊ทํธ๋ฆฌ, ์ํ๋ณ์ ์ค๋ช |
| ์์ด | ์์ ๊ฒฝ์ฐ์ ์, used[] ํ์ฉ | ์ค๋ณต๋ฐฉ์ง, ์ํ ๋ณต์ ์ค๋ช |
| N-Queen | ํ๋ณ ๊ฒฐ์ , ์ ํจ์ฑ ๊ฒ์ฆ & ๊ฐ์ง์น๊ธฐ | ๋ณด๋์ค๊ณ, ์กฐ๊ฑดํ์ , ๋ฐฑํธ๋ํน |
| Sudoku | ์ ๋จ์ ๊ฐ ์๋, ์คํจ ์ ๋ฐฑํธ๋ | ์ ๋ ฅ์์, ์ ํจ๊ฒ์ฆ ์ค์ |
| Word Search | DFS + ๋ฐฉ๋ฌธ์ฒดํฌ, ๋๋๋ฆฌ๊ธฐ ํ์ | ๋ค ๋ฐฉํฅ ํ์, ๋ณต์, ์ ํจ์ฑ |
| Combination Sum | ํฉ์ด ๋๋ ์กฐํฉ, ๋ฐ๋ณต ํ์ฉ | target ๊ฐ์, ๋ฐ๋ณต ์ฌ๊ท ๊ตฌ์กฐ |
| ๊ดํธ ์์ฑ | '(', ')' ๊ฐ์ ์กฐ๊ฑด ์ ์ด | ๊ท ํยท์ ํจ์กฐํฉ, ์ํ ์ถ์ |
๐ก ํ
- ํญ์ **๊ธฐ์ ์กฐ๊ฑด, ์ฌ๊ท ํธ์ถ, ๋๋๋ฆผ(์ํ ๋ณต๊ตฌ)**์ ๊ฐ์กฐํ์ธ์.
- ํจ์จํ๋ฅผ ์ํ ๊ฐ์ง์น๊ธฐ, ๋ฐฉ๋ฌธ ์ฒดํฌ ๋ฑ๋ ๋ฐ๋์ ์ค๋ช !
- ์์ฝ๋ฉ ์์๋ ์ฌ๊ทํธ๋ฆฌ ๋ชจ์๋๋ ๋ณด์ฌ์ฃผ๋ฉด ๋ ์ข์ต๋๋ค.
references